package com.bestcem.xm.qdes.config;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.bestcem.xm.common.core.annotation.XmGet;
import com.bestcem.xm.common.core.annotation.XmGetQueryWebProperty;
import com.bestcem.xm.common.core.annotation.XmSqlInjection;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.bestcem.xm.component.security.constant.SecurityConstants;
import com.bestcem.xm.component.security.context.SecurityContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 反射映射实体类，主要是自动填充orgId和userId及
 * 前端传递参数和后台接收bean filed不一致问题XmGetQueryWebProperty
 *
 * @author chenglong.yue <chenglong.yue@idiaoyan.com>
 * @date 2022/8/4 15:47
 */
@Slf4j
public class QueryArgumentResolver implements HandlerMethodArgumentResolver {

    /**
     * 预编译SQL过滤正则表达式
     */
    private static final Pattern SQL_PATTERN = Pattern.compile("(\\?:')|(\\?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(select|update|and|or|delete|insert|trancate|char|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)", Pattern.CASE_INSENSITIVE);

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 如果方法参数上带@XmGet注解，就执行这个参数解析器
        return parameter.hasParameterAnnotation(XmGet.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String userId = SecurityContextHolder.getUserId();
        String orgId = SecurityContextHolder.getOrgId();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);

        Class<?> pclazz = parameter.getParameterType();
        Object resultObj = pclazz.newInstance();

        Field field1 = ReflectionUtils.findField(pclazz, SecurityConstants.USER_ID);
        if (field1 != null) {
            ReflectionUtils.makeAccessible(field1);
            ReflectionUtils.setField(field1, resultObj, userId);
        }

        Field field2 = ReflectionUtils.findField(pclazz, SecurityConstants.ORG_ID);
        if (field2 != null) {
            ReflectionUtils.makeAccessible(field2);
            ReflectionUtils.setField(field2, resultObj, orgId);
        }

        Field[] fields = pclazz.getDeclaredFields();
        for (Field field : fields) {
            ReflectionUtils.makeAccessible(field);
            String fieldName = field.getName();
            assert request != null;
            String tv = request.getParameter(getUrlParamName(field, fieldName));
            if (Objects.isNull(tv)) {
                continue;
            }
            // sql注入逻辑
            if (field.isAnnotationPresent(XmSqlInjection.class)) {
                isValid(tv);
            }

            // 根据类型拓展
            Class<?> fclazz = field.getType();
            if (fclazz.isAssignableFrom(List.class)) {
                Optional.of(tv)
                        .filter(StrUtil::isNotEmpty)
                        .ifPresent(v -> ReflectionUtils.setField(field, resultObj, Arrays.asList(v.split(","))));
            } else if (fclazz.isAssignableFrom(Boolean.class)) {
                Optional.of(tv)
                        .filter(StrUtil::isNotEmpty)
                        .ifPresent(v -> ReflectionUtils.setField(field, resultObj, booleanOf(v)));
            } else if (fclazz.isAssignableFrom(Integer.class)) {
                Optional.of(tv)
                        .filter(StrUtil::isNotEmpty)
                        .ifPresent(v -> ReflectionUtils.setField(field, resultObj, Integer.valueOf(v)));
            } else if (fclazz.isAssignableFrom(Double.class)) {
                Optional.of(tv)
                        .filter(StrUtil::isNotEmpty)
                        .ifPresent(v -> ReflectionUtils.setField(field, resultObj, Double.valueOf(v)));
            } else {
                ReflectionUtils.setField(field, resultObj, tv);
            }
        }
        return resultObj;
    }

    private String getUrlParamName(Field field, String fieldName) {
        String urlParamName;
        if (field.isAnnotationPresent(XmGetQueryWebProperty.class)) {
            String xwValue = field.getAnnotation(XmGetQueryWebProperty.class).value();
            urlParamName = StringUtils.hasText(xwValue) ? xwValue : fieldName;
        } else {
            urlParamName = fieldName;
        }
        return urlParamName;
    }

    private void isValid(String source) {
        if (StringUtils.hasText(source)) {
            Matcher matcher = SQL_PATTERN.matcher(source);
            if (matcher.find()) {
                log.error("包含非法字符{}", source);
                throw new BusinessException("排序字段存在sql注入风险,疑似被攻击");
            }
        }
    }

    private Boolean booleanOf(String s) {
        List<String> list = ListUtil.of("yes", "true", "t", "y", "1", "ok", "on", "o");
        return list.contains(s.toLowerCase());
    }

}


